//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
//
// Map rendering routines.
//

# include "StdAfx.h"
# include "Map.h"
# include "TypesDraw.h"
# include "Settings.h"
# include "Levels.h"
# include "Load.h"
# include "Status.h"
# include "MapView.h"
# include "DrawRoadClass.h"
# include "DrawNodes.h"
# include "DrawAddress.h"
# include "Scale.h"
# include "Position.h"
# include "Selection.h"
# include "DrawDirIndicator.h"
# include "DrawNumbers.h"
# include "DrawHelpers.h"
# include "ParseHelpers.h"
# include "fast_floor.h"
# include "Options.h"
# include "Wrappers.h"
# include "Globals.h"

static
bool _DrawRgn (
	HDC _hDC, const rect_t & _rectVisible, const rgn_t & _rgn, POINT * _pts, size_t _cMaxPoints,
	bool _bTransparentPolygons, bool _bTransparentPolygonFill, HBRUSH _hbrBG,
	bool _bShowAttachments
) {
	const type_desc_t * const pTD = _rgn.GetTypeDesc ();
	assert (pTD);
	if (! pTD)
		return false;

	bool bDrawLabel = false;

	const RECT rectWnd = {0, 0, g_lWidth, g_lHeight};

	// For each element of the object...
	for (size_t cElement = 0; cElement < _rgn.elements.size (); ++ cElement) {
		if (g_bStop)
			return false;

		const rgn_element_t & element = _rgn.elements [cElement];
		if (element.bDeleted)
			continue;

		const points_t & points = element.points;
		const size_t cPoints = points.size ();
		if (cPoints == 0)
			continue;
		assert (cPoints <= _cMaxPoints);
		if (cPoints > _cMaxPoints)
			continue;

		if (! _rgn.IsWTR () && ! LevelIsVisible (element.cLevel, _rgn.uchEndLevel))
			continue;

		// Calculate screen coordinates.
		if (! (_rgn.IsPolyline () && _rgn.IsWTR ()))
			for (size_t cPoint = 0; cPoint < cPoints; ++ cPoint) {
				const point_t & pt = points [cPoint];
				_pts [cPoint].x = XToScreen (pt.x);
				_pts [cPoint].y = YToScreen (pt.y);

				if (g_bStop)
					return false;
			}

		if (_rgn.IsPolyline ()) {
			if (element.rectBound.Width ()*g_kX < 2.f && element.rectBound.Height ()*g_kY < 2.f)
				// The element is too short.
				continue;

			if (_rgn.IsWTR ()) {
				if (_bShowAttachments && (_rgn.pFileRef == NULL || _rgn.pFileRef->bVisible)) {
					// Draw track or route.
					COLORREF colLine;
					size_t cLineWidth;
					if (_rgn.IsTrack ()) {
						const track_info_t * const pExtra = _rgn.pExtra ? _rgn.pExtra->wtr.track () : NULL;

						if (pExtra && (pExtra->wFlags & track_info_t::defLineColor))
							colLine = pExtra->colLine;
						else
							colLine = GetDefaultTrackColor ();

						if (pExtra && (pExtra->wFlags & track_info_t::defLineWidth))
							cLineWidth = pExtra->wLineWidth;
						else
							cLineWidth = GetDefaultTrackWidth ();
					} else if (_rgn.IsRoute ()) {
						const route_info_t * const pExtra = _rgn.pExtra ? _rgn.pExtra->wtr.route () : NULL;

						if (pExtra && (pExtra->wFlags & route_info_t::defLineColor))
							colLine = pExtra->colLine;
						else
							colLine = GetDefaultRouteColor ();

						cLineWidth = GetDefaultRouteWidth ();
					}

					const HPEN hpenTrack = ::CreatePen (PS_SOLID, cLineWidth, colLine);
					::SelectObject (_hDC, hpenTrack);

					point_t ptPrev = points [0];

					size_t cPoint = 1;
					while (true) {
						// Omit all chains until visible rect intersection.
						for (; cPoint < cPoints; ++ cPoint) {
							const point_t & pt = points [cPoint];

							rect_t rectChain;
							rectChain.x0 = __min (pt.x, ptPrev.x);
							rectChain.y0 = __min (pt.y, ptPrev.y);
							rectChain.x1 = __max (pt.x, ptPrev.x);
							rectChain.y1 = __max (pt.y, ptPrev.y);
							if (_rectVisible.Intersects (rectChain))
								break;
							ptPrev = pt;

							if (g_bStop)
								return false;
						}
						if (cPoint >= cPoints)
							break;

						size_t cScreenPoints = 0;
						_pts [cScreenPoints].x = XToScreen (ptPrev.x);
						_pts [cScreenPoints].y = YToScreen (ptPrev.y);
						++ cScreenPoints;

						for (; cPoint < cPoints; ++ cScreenPoints, ++ cPoint) {
							const point_t & pt = points [cPoint];

							assert (cScreenPoints < _cMaxPoints);
							_pts [cScreenPoints].x = XToScreen (pt.x);
							_pts [cScreenPoints].y = YToScreen (pt.y);

							rect_t rectChain;
							rectChain.x0 = __min (pt.x, ptPrev.x);
							rectChain.y0 = __min (pt.y, ptPrev.y);
							rectChain.x1 = __max (pt.x, ptPrev.x);
							rectChain.y1 = __max (pt.y, ptPrev.y);
							ptPrev = pt;
							if (! _rectVisible.Intersects (rectChain)) {
								++ cScreenPoints;
								break;
							}

							if (g_bStop)
								return false;
						}

						::Polyline (_hDC, _pts, cScreenPoints);

						if (_rgn.IsRoute () || _rgn.pFileRef == NULL || _rgn.pFileRef->bShowDirection) {
							DrawDirIndicator (_hDC, _pts, cScreenPoints, & rectWnd, true);
							::SelectObject   (_hDC, hpenTrack);
						}
					}

					::SelectObject (_hDC, g_hpenBlackThin);
					::DeleteObject (hpenTrack);

					bDrawLabel = true;
					if (_rgn.IsRoute ()) {
						const route_info_t * const pExtra = _rgn.pExtra ? _rgn.pExtra->wtr.route () : NULL;
						if (pExtra && (pExtra->wFlags & route_info_t::defHideLabel))
							bDrawLabel = false;
					}
				}
			} else {
				// Draw polyline.
				if (g_bShowRoadClasses) {
					DrawRoadClass (_hDC, _rgn, _pts, cPoints);
				} else {
					if (g_bHighlightAddresses) {
						::SelectObject (_hDC, GetPolylineAddressContourPen ());
						::Polyline (_hDC, _pts, cPoints);

						::SelectObject (_hDC, AddressToPen (_rgn));
						::Polyline (_hDC, _pts, cPoints);
					} else {
						assert (pTD->phPen);
						::SelectObject (_hDC, * pTD->phPen);

						::Polyline (_hDC, _pts, cPoints);
						if (pTD->phPen2) {
							::SelectObject (_hDC, * pTD->phPen2);
							::Polyline (_hDC, _pts, cPoints);
						}
					}
				}
				bDrawLabel = true;

				if (_rgn.dwFlags & rgn_t::fDirIndicator)
					DrawDirIndicator (_hDC, _pts, cPoints, & rectWnd);

				// Show RG nodes, if any.
				if (g_bShowRGNodes && element.pExtra) {
					::SelectObject (_hDC, g_hpenBlackThin);

					const point_extras_t & PointExtras = element.pExtra->PointExtras;
					const size_t cPointExtras = PointExtras.size ();
					for (size_t c = 0; c < cPointExtras && c < cPoints; ++ c) {
						const rgnode_t * const pNode = PointExtras [c].GetRGNode ();
						if (pNode == NULL)
							continue;

						const POINT & pt = _pts [c];
						if (
							pt.x < 0 || pt.x >= g_lWidth ||
							pt.y < 0 || pt.y >= g_lHeight
						)
							continue;

						const point_t & point = points [c];

						// Check misposition.
						const point_t * const pPoint0 = pNode->pExtra != NULL ? reinterpret_cast<const point_t *> (pNode->pExtra) : NULL;
						const bool bMisposition = pPoint0 && ! (* pPoint0 == point);
						if (pNode->pExtra == NULL)
							pNode->pExtra = const_cast<point_t *> (& point);

						++ pNode->btDrawCount;
						if (pNode->btDrawCount == pNode->btPolylines || bMisposition) {
							// Fill color of RG node mark.
							::SelectObject (_hDC, NodeCardinalityToBrush (bMisposition ? 0 : pNode->btCardinality));
							// Contour color of RG node mark.
							::SelectObject (_hDC, NodeContourPen (pNode->bHasRestrictions));

							// Draw RG node.
							if (pNode->bIsExternal)
								DrawDiamond (_hDC, pt.x, pt.y, 6);
							else
								DrawSquare  (_hDC, pt.x, pt.y, bMisposition ? 4 : 3);
						}
					}
				}

				// Show numbers.
				if (g_bShowNumbers && element.pExtra)
					DrawNumbers (_hDC, _rgn, element, _pts, cPoints);
			}
		} else if (_rgn.IsPolygon ()) {
//			if (element.fLengthOrArea*g_kY*g_kX < 4)
			if (element.rectBound.Width ()*g_kX < 2. || element.rectBound.Height ()*g_kY < 2.)
				// The element is too small.
				continue;

			// Draw polygon.
			if (_bTransparentPolygons) {
				assert (pTD->hbrTransparentPolygon);
				if (_bTransparentPolygonFill)
					::SelectObject (_hDC, element.bHole ? g_hbrWhiteHatched : pTD->hbrTransparentPolygon);
				else
					::SelectObject (_hDC, g_hbrNull);

				assert (pTD->hpenTransparentPolygon);
				::SelectObject (_hDC, pTD->hpenTransparentPolygon);
			} else {
				HBRUSH hBrush = NULL;
				if (g_bHighlightAddresses) {
					hBrush = AddressToBrush (_rgn, rkPolygon);
				} else {
					assert (pTD->phBrush);
					hBrush = * (pTD->phBrush);
				}
				::SelectObject (_hDC, element.bHole ? _hbrBG : hBrush);

				assert (pTD->phPen);
				HPEN hPen = * (pTD->phPen);
				if (g_bHighlightAddresses) {
					if (PolygonAddressIsIncomplete (_rgn))
						hPen = GetPolygonIncompleteAddressPen ();
				}
				::SelectObject (_hDC, hPen);
			}
			::Polygon (_hDC, _pts, cPoints);

			bDrawLabel = true;
		}
	}

	// Draw point.
	if (pTD->phCursor)
		for (size_t cOrigin = 0; cOrigin < _rgn.origins.size (); ++ cOrigin) {
			const rgn_origin_t & origin = _rgn.origins [cOrigin];
			if (origin.bDeleted)
				continue;

			if (! _rgn.IsWTR () && ! LevelIsVisible (origin.cLevel, _rgn.uchEndLevel))
				continue;

			const int x = XToScreen (origin.point.x);
			const int y = YToScreen (origin.point.y);

			if (_rgn.IsWTR ()) {
				if (_bShowAttachments && (_rgn.pFileRef == NULL || _rgn.pFileRef->bVisible)) {
					::DrawIcon (_hDC, x - g_nCursorShiftX_Waypoint, y - g_nCursorShiftY_Waypoint, g_hcurWaypoint);

					bDrawLabel = true;
					if (_rgn.IsWaypoint ()) {
						const waypoint_info_t * const pExtra = _rgn.pExtra ? _rgn.pExtra->wtr.wpt () : NULL;
						if (pExtra && pExtra->GetShow () == waypoint_info_t::showSymbol)
							bDrawLabel = false;
					}
				}
			} else {
				if (g_bHighlightAddresses) {
					::SelectObject (_hDC, AddressToBrush (_rgn, rkPoint));

					HPEN hPen = GetPointAddressContourPen ();
					if (PointAddressIsIncomplete (_rgn))
						hPen = GetPointIncompleteAddressPen ();
					::SelectObject (_hDC, hPen);

					DrawSquare (_hDC, x, y, 9);
				}

				// Draw icon.
				POINT ptCursorShift;
				pTD->GetCursorShift (ptCursorShift);
				::DrawIcon (_hDC, x - ptCursorShift.x, y - ptCursorShift.y, * pTD->phCursor);
				bDrawLabel = true;
			}
		}

	return bDrawLabel;
}

void map_t::Draw (HDC _hDC, const rect_t & _rectVisible, size_t _cProgressStart, size_t _cProgressEnd, bool _bShowAttachments) const {
	const long X0 = XToScreen (_rectVisible.x0);
	const long X1 = XToScreen (_rectVisible.x1) + 1;
	const long Y0 = YToScreen (_rectVisible.y0) + 1;
	const long Y1 = YToScreen (_rectVisible.y1);
	const long lWidth  = X1 - X0;
	const long lHeight = Y0 - Y1;

	if (g_bHighlightAddresses)
		StartDrawAddresses ();

	CDCSaver dc_saver (_hDC);

	const bool bTransparentPolygonsFill = GetTransparentPolygonFill ();

	const size_t cProgressRange = _cProgressEnd - _cProgressStart;
	SetProgressStatus (_cProgressStart);

	const HBRUSH hbrBG = ::CreateSolidBrush (GetBackgroundColor ());
	CGdiObjectHolder hbrBG_Holder (hbrBG);

	//
	// Draw the attached renderers and images first.
	//
	if (_bShowAttachments) {
		for (renderers_t::const_iterator iRenderer = renderers.begin (); iRenderer != renderers.end (); ++ iRenderer) {
			const renderer_base_t * const pRenderer = * iRenderer;
			if (pRenderer->bDeleted || ! pRenderer->bVisible)
				continue;
			pRenderer->Draw (_hDC, _rectVisible, 0, 0);
		}
	}

	// Prepare RG nodes.
	if (g_bShowRGNodes)
		const_cast<map_t *> (this)->CountRGNodes (false);

	for (rgnodes_t::const_iterator i = rgnodes.begin (); i != rgnodes.end (); ++ i) {
		const rgnode_t & node = * i;
		node.pExtra = NULL;
	}

	// Prepare DC.
	::SetROP2       (_hDC, R2_COPYPEN);
	::SetBkMode     (_hDC, TRANSPARENT);
	::SetBkColor    (_hDC, GetBackgroundColor ());
	::SelectObject  (_hDC, g_hFont);
	::SetBrushOrgEx (_hDC, -g_lX, g_lY, NULL);

	// Prepare buffer of points to draw polylines and polygons.
	const bool bHuge = cMaxPointsInElement > 30000;
	POINT * const pts = bHuge ? new POINT [cMaxPointsInElement] : reinterpret_cast<POINT *> (::alloca (sizeof (POINT)*cMaxPointsInElement));
	CArrayHolder<POINT> pts_Holder (bHuge ? pts : NULL);

	// The list of objects with labels to draw them in second pass.
	typedef std::list<const rgn_t *> listLabeledRgns_t;
	listLabeledRgns_t listLabeledRgns;
	size_t cLabels = 0;
/*
	const size_t cCellWidth = 100;
	const size_t cCellHeight = 20;
	const size_t cCellsX = lWidth/cCellWidth + 1;
	const size_t cCellsY = lHeight/cCellHeight + 1;
	bool * const pLabelCells = reinterpret_cast<bool *> (::alloca (sizeof (bool)*cCellsX*cCellsY));
	for (size_t cCells = 0; cCells < cCellsX*cCellsY; ++ cCells)
		pLabelCells [cCells] = false;
*/
	//
	// Draw objects without labels.
	// NOTE: do it in reverse order: polygons are first.
	//
	for (int cKind = rkMax - 1; cKind >= 0; -- cKind) {
		const rgns_t & rgns = layers [cKind].rgns;
		const size_t cRgns = layers [cKind].cRgns;
		if (cRgns == 0)
			continue;

		size_t cRgn = 0;
		for (rgns_t::const_iterator iRgn = rgns.begin (); iRgn != rgns.end (); ++ iRgn, ++ cRgn) {
			if (g_bStop)
				return;

			const rgn_t & rgn = * iRgn;
			if (rgn.IsDeleted ())
				continue;

			// Rough intersection check.
			if (rgn.rectBound.Intersects (_rectVisible)) {
				const bool bDrawLabel = _DrawRgn (
					_hDC, _rectVisible, rgn, pts, cMaxPointsInElement,
					g_bTransparentPolygons, bTransparentPolygonsFill, hbrBG,
					_bShowAttachments
				);

				// Prepare labels to be drawn in the 2nd pass.
				if (bDrawLabel && ! rgn.strLabel.empty () && g_bShowLabels) {
					bool bAddLabel = true;
/*
					if (rgn.bPtLabelDefined) {
						const int x = XToScreen (rgn.ptLabel.x);
						const int y = YToScreen (rgn.ptLabel.y);
						if (0 <= x && x < lWidth && 0 <= y && y < lHeight) {
							const size_t cIndexX = ((x + g_lX)/cCellWidth) % cCellsX;
							const size_t cIndexY = ((y + g_lY)/cCellHeight) % cCellsY;
							const size_t cIndex = cIndexX*cCellsY + cIndexY;
							assert (cIndex < cCellsX*cCellsY);
							if (pLabelCells [cIndex])
								bAddLabel = false;
							else
								pLabelCells [cIndex] = true;
						}
					}
*/
					// Exclude labels whose width > objects's size.
					if (rgn.ucLabelWidthBy8 != 0 && rgn.fSize2 != 0)
						bAddLabel = rgn.ucLabelWidthBy8*8UL*rgn.ucLabelWidthBy8*8UL < rgn.fSize2*g_kY*g_kY;

					if (bAddLabel) {
						listLabeledRgns.push_back (& rgn);
						++ cLabels;
					}
				}
			}

			if (g_bShowLabels)
				SetProgressStatus (_cProgressStart + fast_floor (.5f + cProgressRange*(0.125f*cRgn/cRgns + 0.125f*(3 - cKind))));
			else
				SetProgressStatus (_cProgressStart + fast_floor (.5f + cProgressRange*(0.25f*cRgn/cRgns + 0.25f*(3 - cKind))));
		}
	}

	// Draw labels in the 2nd pass.
	if (g_bShowLabels) {
		const size_t cMaxLabels = lWidth*lHeight/2000;
		if (cMaxLabels > 0) {
			const size_t cExcess = cLabels/cMaxLabels + 1;
			assert (cExcess >= 1);

			size_t cLabel = 0;
			for (listLabeledRgns_t::const_iterator i = listLabeledRgns.begin (); i != listLabeledRgns.end (); ++ i, ++ cLabel) {
				const rgn_t & rgn = ** i;

				if ((reinterpret_cast<size_t> (& rgn)/sizeof (rgn)) % cExcess != 0)
					continue;

				rgn.GetTypeDesc ()->pfDrawLabel (_hDC, rgn);

				SetProgressStatus (_cProgressStart + fast_floor (.5f + cProgressRange/2 + cProgressRange*.5f*cLabel/cLabels));
			}
		}
	}

	SetProgressStatus (_cProgressEnd);
}
